﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GrammarGenericSolver.IO;
using GrammarGenericSolver.ADS;
using GrammarGenericSolver.Exceptions;

namespace GrammarGenericSolver.Model
{
	/// <summary>
	/// Gramática regular e livre de contexto.
	/// </summary>
	public class Grammar
	{
		private string _objective;
		/// <summary>
		/// Objetivo da gramática. Primeira linha do arquivo.
		/// </summary>
		public string Objective
		{
			get { return _objective; }
			set { _objective = value; }
		}


		private string _file;
		/// <summary>
		/// Arquivo da gramática.
		/// </summary>
		public string File
		{
			get { return _file; }
			set { _file = value; }
		}

		private NonTerminal _initialSymbol;
		/// <summary>
		/// Símbolo inicial da gramática.
		/// </summary>
		public NonTerminal InitialSymbol
		{
			get { return _initialSymbol; }
			set { _initialSymbol = value; }
		}

		private Dictionary<string, NonTerminal> _nonTerminalList;
		/// <summary>
		/// Lista de não terminais de uma gramática regular. Deve estar na ordem das regras de produção.
		/// </summary>
		public Dictionary<string, NonTerminal> NonTerminalList
		{
			get { return _nonTerminalList; }
			set { _nonTerminalList = value; }
		}

		private Dictionary<string, Symbol> _terminalList;
		/// <summary>
		/// Lista de terminais de uma gramática regular.
		/// </summary>
		public Dictionary<string, Symbol> TerminalList
		{
			get { return _terminalList; }
			set { _terminalList = value; }
		}

		private DerivationTree _tree;
		/// <summary>
		/// Árvore de derivação da gramática.
		/// </summary>
		public DerivationTree Tree
		{
			get { return _tree; }
			set { _tree = value; }
		}

		/// <summary>
		/// Construtor default para uma gramática vazia.
		/// </summary>
		public Grammar() : this("")
		{
		}

		/// <summary>
		/// Path do arquivo com a gramática.
		/// </summary>
		/// <param name="path"></param>
		public Grammar(string path)
		{
			if (!path.Equals(""))          
			{
				File = FileHandler.Instance.ReadFile(path);
				GenerateGrammar();
			}
		}

		/// <summary>
		/// Método para gerar e organizar os objetos da gramática de acordo com o File.
		/// </summary>
		private void GenerateGrammar()
		{
			//Inicia a leitura do arquivo
			//Todas as linhas começam com #, no entanto, as linhas correspondentes às produções começam com @

			//Divido o arquivo para que cada linha seja uma posição no vetor fileLines
			string[] fileLines = File.Split('\r');

			int index = 0;

			//A primeira linha do arquivo corresponde ao objetivo da gramática, ignoramos o # do começo em todas as linhas
			string objective = fileLines[index++].Substring(1);
			//Verificação se o objetivo usa mais de uma linha dentro do arquivo e as adiciona até que se encontre uma próxima linha começando com #
			while (!fileLines[index].StartsWith("#"))
			{
				objective += " " + fileLines[index++];
				if (index == fileLines.Length)
				{
					throw new CorruptedFileException();
				}
			}
			Objective = objective;

			//Próxima linha deve corresponder ao símbolo inicial da gramática
			string initialSymbol = fileLines[index++].Substring(1);
			InitialSymbol = new NonTerminal(initialSymbol);

			//Próxima linhas correspondem ao conjunto de terminais
			string terminalLine = "";
			do
			{
				terminalLine = fileLines[index++].Substring(1);

				//Pega a lista de terminais que estão separados por um espaço vazio
				string[] terminalList = terminalLine.Split(' ');
				foreach (string terminalItem in terminalList)
				{
					Symbol terminal = new Symbol(terminalItem);
					TerminalList.Add(terminalItem, terminal);
				}
			} while (!fileLines[index].StartsWith("#"));

			//Próximas linhas correspondem ao conjunto de não terminais, incluindo o símbolo inicial
			string nonTerminalLine = "";
			do
			{
				nonTerminalLine = fileLines[index++].Substring(1);

				//Pega a lista de não terminais que estão separados por um espaço vazio
				string[] nonTerminalList = nonTerminalLine.Split(' ');
				foreach (string nonTerminalItem in nonTerminalList)
				{
					NonTerminal nonTerminal = new NonTerminal(nonTerminalItem);
					NonTerminalList.Add(nonTerminalItem, nonTerminal);
				}
			} while (!fileLines[index].StartsWith("#") && !fileLines[index].StartsWith("@"));

			//Próximas linhas correspondem às produções da gramática.
			string production = "";
			for (; index < fileLines.Length;)
			{
				do{
				    production = fileLines[index++].Substring(1);

				    string nonTerminalIdentifier = GetTextUntil(production, "::=");
				    NonTerminal nonTerminal = NonTerminalList[nonTerminalIdentifier];

				    string productions = GetTextAfter(production, "::=");
				    //Separa as produções pelo | (ou)
				    string[] productionsList = productions.Split('|');

                    //Exemplo: A::=a|b|<B>c
                    foreach (string ruleLine in productionsList)
                    {
                        nonTerminal.Rules.Add(GetComplexRule(ruleLine));
                    }

				} while (!fileLines[index].StartsWith("@"));
			}

            //O construtor da árvore de derivação é responsável por realizar a sua instanciação.
            Tree = new DerivationTree(this);
		}

        /// <summary>
        /// Método para dado uma linha de produção do tipo: a b <B> <C>. Pegar os diferentes símbolos e adicioná-los na ordem à regra.
        /// </summary>
        /// <param name="ruleLine"></param>
        /// <returns></returns>
        private Rule GetComplexRule(string ruleLine)
        {
            Rule rule = new Rule();

            string[] ruleLineElements = ruleLine.Split(' ');
            foreach (string element in ruleLineElements)
            {
                if (element.Contains("<"))
                {
                    //Remove o < e o > da string para ficar apenas com o nome do não terminal.
                    string nonTerminalElement = element.Substring(1, element.Length - 2);

                    NonTerminal nonTerminal = NonTerminalList[nonTerminalElement];
                    if (nonTerminal == null)
                    {
                        //Se não achar o não terminal, é porque ele não foi definido corretamente na gramática.
                        throw new CorruptedFileException();
                    }

                    rule.AddSymbol(nonTerminal);
                }
                else
                {
                    Symbol terminal = TerminalList[element];

                    if (terminal == null)
                    {
                        //Se não achar o terminal, é porque ele não foi definido corretamente na gramática.
                        throw new CorruptedFileException();
                    }

                    rule.AddSymbol(terminal);
                }
            }

            if (!rule.HasSymbols())
            {
                //Não pode existir regra sem símbolos.
                throw new CorruptedFileException();
            }

            return rule;
        }

		/// <summary>
		/// Método para dado um texto e um token, retornar o subtexto do ínicio até o token passado. Exemplo:
		/// text = "aaaa--bbb"
		/// token = "--"
		/// output = "aaa"
		/// </summary>
		/// <param name="text"></param>
		/// <param name="token"></param>
		/// <returns></returns>
		private string GetTextUntil(string text, string token)
		{
			int index = text.IndexOf(token);
			if (index != -1)
			{
				return text.Substring(0, index);
			}
			else
			{
				return text;
			}
		}

		/// <summary>
		/// Método para dado um texto e um token, retornar o subtexto do token até fim. Exemplo:
		/// text = "aaaa--bbb"
		/// token = "--"
		/// output = "bbb"
		/// </summary>
		/// <param name="text"></param>
		/// <param name="token"></param>
		/// <returns></returns>
		private string GetTextAfter(string text, string token)
		{
			int index = text.IndexOf(token);
			if (index != -1)
			{
				int size = text.Length - index;
				return text.Substring(index, size);
			}
			else
			{
				return text;
			}
		}

		/// <summary>
		/// Dada uma sentença, verifica se a gramática pode gerar esta sentença
		/// </summary>
		/// <param name="sentence"></param>
		/// <returns></returns>
		public bool VerifySentence(string sentence)
		{
			return false;
		}
	}
}
